# Copyright (c) 2023 orchy <orchy12@proton.me>
# This file is licensed under the MIT/Expat license.
# You should have received a copy of the license with this software, if not see
# https://spdx.org/licenses/MIT.html
import std/sequtils
import std/strutils
import std/terminal
import std/parsecfg
import std/unicode
import std/os
import x1b

let ostream* = stdout
let config = loadConfig(getConfigDir() & "neat/tui.conf")

type
  TuiPosMode* {.pure.} = enum
    raw, centered
  TuiPos* = object
    x*, y*: float
    xmode*: TuiPosMode = centered
    ymode*: TuiPosMode = centered
  TuiPosC = object
    x*, y*: float
    xmode*: TuiPosMode = centered
    ymode*: TuiPosMode = centered
  TuiText* = object
    pos*: TuiPos
    text*: string
    border*: bool
  TuiSingleSelection* = object
    pos*: TuiPos
    selected*: int
    options*: seq[string]
    border*: bool


proc longest(self: seq[string]): string =
  for s in self:
    if s.runeLen > result.runeLen:
      result = s


proc xy(self: TuiPos, centerOffset: TuiPosC = TuiPosC(x: 0, y: 0)): TuiPosC =
  var
    x: float = self.x * terminalWidth().float
    y: float = self.y * terminalHeight().float

  if self.xmode == centered:
    x += centerOffset.x
  if self.ymode == centered:
    y += centerOffset.y

  return TuiPosC(x: x, y: y, xmode: raw, ymode: raw)


proc goto(self: TuiPos, centerOffset: TuiPosC = TuiPosC(x: 0, y: 0)) =
  let pos = xy(self, centerOffset)
  setCursorPos(pos.x.int, pos.y.int)


proc border(selfPos: TuiPos, centerOffset: TuiPosC, size: TuiPosC) =
  selfPos.goto(centerOffset)
  cursorBackward 1
  ostream.write config.getSectionValue("Border", "ulCorner")

  for _ in countup(0, size.x.int):
    ostream.write config.getSectionValue("Border", "uEdge")
  ostream.write config.getSectionValue("Border", "urCorner")

  for _ in countup(0, size.y.int):
    cursorDown 1
    cursorBackward 1
    ostream.write config.getSectionValue("Border", "rEdge")

  cursorDown 1
  cursorBackward 1
  ostream.write config.getSectionValue("Border", "brCorner")

  for _ in countup(0, size.x.int):
    cursorBackward 2
    ostream.write config.getSectionValue("Border", "bEdge")
  cursorBackward 2
  ostream.write config.getSectionValue("Border", "blCorner")

  for _ in countup(0, size.y.int):
    cursorUp 1
    cursorBackward 1
    ostream.write config.getSectionValue("Border", "lEdge")


proc validate*(self: var TuiSingleSelection) =
  if self.selected < 0:
    self.selected = 0
  elif self.selected > self.options.len - 1:
    self.selected = self.options.len - 1


proc render*(self: TuiSingleSelection) =
  let
    xPos = -(self.options.longest.runeLen / 2) - config.getSectionValue(
      "TuiSingleSelection", "selected").runeLen.float
    yPos = -self.options.len / 2
    pos = TuiPosC(x: xPos, y: yPos)

  for i, s in self.options:
    self.pos.goto(pos)
    cursorDown i+1
    if self.selected == i:
      ostream.write config.getSectionValue("TuiSingleSelection", "selected")
    else:
      ostream.write config.getSectionValue("TuiSingleSelection", "unSelected")
    ostream.write s

  if self.border:
    border(self.pos, TuiPosC(x: xPos, y: yPos), TuiPosC(x: self.options.
           longest.len.float + 3.0, y: self.options.len.float - 1.0))

  flushFile ostream


proc selectedString*(self: TuiSingleSelection): string =
  self.options[self.selected]


proc render*(self: TuiText) =
  let cleanText: string = x1bParse(self.text).
                          filterIt(it.kind == c).
                          mapIt(it.c).
                          join

  if self.border:
    border(self.pos, TuiPosC(x: -cleanText.runeLen / 2, y: -1), TuiPosC(
           x: cleanText.runeLen / 2 + 2, y: 0.0))

  self.pos.goto(TuiPosC(x: -cleanText.runeLen / 2, y: 0))
  ostream.write self.text

  flushFile ostream


proc TuiBegin*() =
  ostream.hideCursor()
  ostream.write "\x1b7"          # save cursor position
  ostream.write "\x1b[?47h"      # save screen
  ostream.eraseScreen()
proc TuiEnd*() =
  ostream.write "\x1b[?47l"      # restore screen
  ostream.write "\x1b8"          # restore cursor position
  ostream.showCursor()

